#include<stdio.h>
#include<stdlib.h>
#include "ast.h"
#include "tac.h"
#include "symbol_table.h"

/**
 * File where assembly code will be written
 */
FILE* assembly_code;


/**
 * Generate a Tac code from AST structure
 *
 * @param root AST root
 * @return TAC code
 */
TAC* ast_generate_tac_code(AST *root);

/**
 * Generates Assembly code for x86 - AT&T syntax
 *
 * @param tac_code TAC code used as source to generate assembly
 * @param hash the hash table used to find identifiers and literals to define global vars in assembly
 * @param assembly_file_name the assembly output file
 */
void code_gen(TAC* tac_code, HASH_TABLE* hash, char* assembly_file_name);

void code_gen_add(FILE* assembly_code, TAC* tac_code);
void code_gen_sub(FILE* assembly_code, TAC* tac_code);
void code_gen_mul(FILE* assembly_code, TAC* tac_code);
void code_gen_div(FILE* assembly_code, TAC* tac_code);
void code_gen_and(FILE* assembly_code, TAC* tac_code);
void code_gen_or(FILE* assembly_code, TAC* tac_code);
void code_gen_comp(FILE* assembly_code, TAC* tac_code, void (* fun)(FILE* assembly_code));
void code_gen_eq(FILE* assembly_code);
void code_gen_ne(FILE* assembly_code);
void code_gen_g(FILE* assembly_code);
void code_gen_l(FILE* assembly_code);
void code_gen_ge(FILE* assembly_code);
void code_gen_le(FILE* assembly_code);
void code_gen_not(FILE* assembly_code, TAC* tac_code);
void code_gen_jzero(FILE* assembly_code, TAC* tac_code);
void code_gen_label(FILE* assembly_code, TAC* tac_code);
void code_gen_copy(FILE* assembly_code, TAC* tac_code);
void code_gen_vec_copy(FILE* assembly_code, TAC* tac_code);
void code_gen_def_int_vec(FILE* assembly_code, HASH_ENTRY* hash_entry);
void code_gen_def_byte_vec(FILE* assembly_code, HASH_ENTRY* hash_entry);
void code_gen_def_int_var(FILE* assembly_code, HASH_ENTRY* hash_entry);
void code_gen_def_byte_var(FILE* assembly_code, HASH_ENTRY* hash_entry);
void code_gen_def_lit_int_var(FILE* assembly_code, HASH_ENTRY* hash_entry);
void code_gen_func_begin(FILE* assembly_code, TAC* tac_code);
void code_gen_func_end(FILE* assembly_code, TAC* tac_code);
void code_gen_mov_op_to_reg(FILE* assembly_code, HASH_ENTRY* entry, char* reg);
void code_gen_call(FILE* assembly_code, TAC* tac_code);
void code_gen_print_num(FILE* assembly_code, TAC* tac_code);
void code_gen_print_str(FILE* assembly_code, TAC* tac_code);
void code_gen_goto(FILE* assembly_code, TAC* tac_code);
void code_gen_vec_ref(FILE* assembly_code, TAC* tac_code);
void code_gen_return(FILE* assembly_code, TAC* tac_code);
void code_gen_tac_arg(FILE* assembly_code, TAC* tac_code);

AST* ast_create(int type, HASH_NODE* symbol0, HASH_NODE* symbol1, AST* s0, AST* s1, AST* s2, AST* s3, int line_number)
{
    AST* newNode = (AST *) calloc(1, sizeof (AST));

    newNode->type = type;
    newNode->symbol[0] = symbol0;
    newNode->symbol[1] = symbol1;
    newNode->sons[0] = s0;
    newNode->sons[1] = s1;
    newNode->sons[2] = s2;
    newNode->sons[3] = s3;
    newNode->line_number = line_number;

    return newNode;
}

void ast_for_each_do(AST* a, void (* fun)(AST* a)) {
    if (a) {
        int i;
        (* fun)(a);
        for (i = 0; i < AST_MAX_SONS; i++) {
            if (a->sons[i]) {
                ast_for_each_do(a->sons[i], fun);
            }
        }
    }
}

char* ast_convert_type(int type) {
    switch (type) {
        case AST_ADD:
            return "AST_ADD";

        case AST_SUB:
            return "AST_SUB";

        case AST_MUL:
            return "AST_MUL";

        case AST_DIV:
            return "AST_DIV";

        case AST_IDENTIFIER:
            return "AST_IDENTIFIER";

        case AST_LIT_INT:
            return "AST_LIT_INT";

        case AST_LIT_CHAR:
            return "AST_LIT_CHAR";

        case AST_ASS:
            return "AST_ASS";

        case AST_LCMD:
            return "AST_LCMD";

        case AST_IF:
            return "AST_IF";

        case AST_IF_ELSE:
            return "AST_IF_THEN";

        case AST_WHILE:
            return "AST_WHILE";

        case AST_PRINT_STRING:
            return "AST_PRINT_STRING";

        case AST_RETURN:
            return "AST_RETURN";

        case AST_READ:
            return "AST_READ";

        case AST_OPERATOR_OR:
            return "AST_OPERATOR_OR";

        case AST_OPERATOR_AND:
            return "AST_OPERATOR_AND";

        case AST_OPERATOR_L:
            return "AST_OPERATOR_L";

        case AST_OPERATOR_LE:
            return "AST_OPERATOR_LE";

        case AST_OPERATOR_G:
            return "AST_OPERATOR_G";

        case AST_OPERATOR_GE:
            return "AST_OPERATOR_GE";

        case AST_OPERATOR_EQ:
            return "AST_OPERATOR_EQ";

        case AST_OPERATOR_NE:
            return "AST_OPERATOR_NE";

        case AST_OPERATOR_NOT:
            return "AST_OPERATOR_NOT";

        case AST_ASS_VEC:
            return "AST_ASS_VEC";

        case AST_CMD_BLOCK:
            return "AST_CMD_BLOCK";

        case AST_FUNC_CALL:
            return "AST_FUNC_CALL";

        case AST_PARAM_LIST:
            return "AST_PARAM_LIST";

        case AST_PARAM:
            return "AST_PARAM";

        case AST_VAR_DEF:
            return "AST_VAR_DEF";

        case AST_VEC_DEF:
            return "AST_VEC_DEF";

        case AST_FUNC_DEF:
            return "AST_FUNC_DEF";

        case AST_BYTE_TYPE:
            return "AST_BYTE_TYPE";

        case AST_INT_TYPE:
            return "AST_INT_TYPE";

        case AST_FUNC_PARAM_LIST:
            return "AST_FUNC_PARAM_LIST";

        case AST_EXPR:
            return "AST_EXPR";

        case AST_PRINT_EXPR:
            return "AST_PRINT_EXPR";

        case AST_DEFS:
            return "AST_DEFS";

        case AST_EXPR_PAR:
            return "AST_EXPR_PAR";
        case AST_VEC_REF:
            return "AST_VEC_REF";

        default: return "UNKNOW";
    }
}

void ast_print_one(AST* n) {
    if (n) {
        printf("ast(");
        printf("%s", ast_convert_type(n->type));

        if (n->symbol[0])
            printf(", symbol0: %s", n->symbol[0]->data->lexeme);
        if (n->symbol[1])
            printf(", symbol1: %s", n->symbol[1]->data->lexeme);
        if (n->sons[0])
            printf(", son0: %s", ast_convert_type(n->sons[0]->type));
        if (n->sons[1])
            printf(", son1: %s", ast_convert_type(n->sons[1]->type));
        if (n->sons[2])
            printf(", son2: %s", ast_convert_type(n->sons[2]->type));
        if (n->sons[3])
            printf(", son3: %s", ast_convert_type(n->sons[3]->type));

        printf(", line number: %d)\n", n->line_number);
    }
}

//GOOD!!!
TAC* do_while(TAC *exp, TAC *cmd) {
    HASH_NODE *label_begin, *label_end;
    TAC *tac_jz, *tac_goto;
    TAC *tac_end, *tac_begin;

    //Create labels and it's respective TACs
    label_begin = make_label();
    label_end = make_label();
    tac_begin = tac_create(TAC_LABEL, label_begin, 0, 0);
    tac_end = tac_create(TAC_LABEL, label_end, 0, 0);

    tac_join(tac_begin, exp); // [label_begin]::[exp]

    tac_jz = tac_create(TAC_JZERO, label_end, exp->res, 0);

    tac_join(exp, tac_jz); // [label_begin]::[exp]::[tac_jz]

    tac_join(tac_jz, cmd); // [label_begin]::[exp]::[tac_jz]::[cmd]

    tac_goto = tac_create(TAC_GOTO, label_begin, 0, 0);

    tac_join(cmd, tac_goto); // [label_begin]::[exp]::[tac_jz]::[cmd]::[tac_goto]

    return tac_join(tac_goto, tac_end); // [label_begin]::[exp]::[tac_jz]::[cmd]::[tac_goto]::[tac_end]
}

//GOOD!!!
TAC* do_bin_op(int type, TAC* l1, TAC* l2)
{
    tac_join(l1, l2);
    HASH_NODE* hash = make_result_var(SYMBOL_INT_TYPE);
    TAC* tac_bin_op = tac_create(type, hash, l1->res, l2->res);
    tac_bin_op = tac_join(l2, tac_bin_op);
    return tac_bin_op;
}

//GOOD!!!
TAC* do_assign(HASH_NODE* var, TAC* expr)
{
    TAC* tac_assign;

    tac_assign = tac_create(TAC_COPY, var, expr->res, 0);
    tac_join(expr, tac_assign);

    return tac_assign;
}

TAC* do_vec_assign(HASH_NODE* var, TAC* index_expr, TAC* expr_value)
{
    TAC* tac_assign;

    tac_assign = tac_create(TAC_VEC_COPY, var, index_expr->res, expr_value->res);
    tac_join(expr_value, tac_join(index_expr, tac_assign));


    return tac_assign;
}

//GOOD!!!
TAC* do_func_param(HASH_NODE* param_id, TAC* type)
{
    TAC* tac_func_param = tac_create(TAC_FUNC_PARAM, param_id, 0, 0);
    tac_join(tac_func_param, type);

    return type;
}

//GOOD!!!
TAC* do_func_dec(HASH_NODE* func_id, TAC* func_type, TAC* param_list, TAC* cmd_list)
{
    TAC* tac_func_begin = tac_create(TAC_FUNC_BEGIN, func_id, 0, 0);
    tac_join(tac_func_begin, func_type);
    TAC* func_after_param;
    if (param_list) {
        func_after_param = tac_join(func_type, param_list);
    } else {
        func_after_param = func_type;
    }

    TAC* func_after_cmd;
    if(cmd_list) {
        func_after_cmd = tac_join(func_after_param, cmd_list);
    } else {
        func_after_cmd = func_after_param;
    }

    TAC* tac_func_end = tac_create(TAC_FUNC_END, func_id, 0, 0);
    return tac_join(func_after_cmd, tac_func_end);
}

// GOOD
TAC* do_return(TAC* expr)
{
    TAC* tac_return = tac_create(TAC_RETURN, expr->res, 0, 0);
    return tac_join(expr, tac_return);
}

// GOOD
TAC* do_print_int(TAC* expr)
{
    TAC* tac_print = tac_create(TAC_PRINT_INT, expr->res, 0, 0);
    tac_print = tac_join(expr, tac_print);

    return tac_print;
}

TAC* do_vec_ref(HASH_NODE* var, TAC* vec_index)
{
    HASH_NODE* result;
    if (var->data->dataType == SYMBOL_INT_TYPE) {
        result = make_result_var(SYMBOL_INT_TYPE);

    } else {
        result = make_result_var(SYMBOL_BYTE_TYPE);
    }
    
    TAC* tac_vec_ref = tac_create(TAC_VEC_REF, result, var, vec_index->res);
    return tac_vec_ref;
}

TAC* do_print_str(HASH_NODE* string)
{
    TAC* tac_print_str = tac_create(TAC_PRINT_STR, string, 0, 0);
    return tac_print_str;
}

// GOOD
TAC *do_if(TAC *exp, TAC *cmd)
{
    HASH_NODE *new_label;
    TAC *tac_jz;
    TAC *final;

    new_label = make_label();
    tac_jz = tac_create(TAC_JZERO, new_label, exp->res, 0);
    tac_join(exp, tac_jz);
    cmd = tac_join(tac_jz, cmd);
    final = tac_create(TAC_LABEL, new_label, 0, 0);
    tac_join(cmd, final);

    return final;
}

//GOOD!!!
TAC *do_if_else(TAC *exp, TAC *if_cmd, TAC *else_cmd)
{
    printf("\n\n##################tamos ai");

    //Create labels for else comands and expression end
    HASH_NODE *label_else, *label_fim;
    label_else = make_label();
    label_fim = make_label();
    //Create label TACs
    TAC *tac_else, *tac_end;
    tac_else = tac_create(TAC_LABEL, label_else, 0, 0);
    tac_end = tac_create(TAC_LABEL, label_fim, 0, 0);
    //Create Jump TACs
    TAC *tac_jz = tac_create(TAC_JZERO, label_else, exp->res, 0);
    TAC *tac_goto = tac_create(TAC_GOTO, label_fim, 0, 0);

    tac_join(exp, tac_jz); // [JZERO]::[EXP]
    tac_join(tac_jz, if_cmd); // [IF_COMMANDS]::[JZERO]::[EXP]
    tac_join(if_cmd, tac_goto); // [GOTO_END]::[IF_COMMANDS]::[JZERO]::[EXP]
    tac_join(tac_goto, tac_else); // [LABEL_ELSE]::[GOTO_END]::[IF_COMMANDS]::[JZERO]::[EXP]
    tac_join(tac_else, else_cmd); // [ELSE_COMMANDS]::[LABEL_ELSE]::[GOTO_END]::[IF_COMMANDS]::[JZERO]::[EXP]
    return tac_join(else_cmd, tac_end); // [LABEL_FIM]::[ELSE_COMMANDS]::[LABEL_ELSE]::[GOTO_END]::[IF_COMMANDS]::[JZERO]::[EXP]
}

//GOOD
TAC* do_var_def(HASH_NODE* var, TAC* expr)
{
    TAC* tac_ref = tac_create(TAC_VAR_DEF, var, NULL, NULL);
    tac_join(tac_ref, expr);
    return expr;
}

TAC* do_vec_def(HASH_NODE* var, HASH_NODE* vec_index, TAC* expr)
{
    TAC* tac_ref = tac_create(TAC_VEC_DEF, var, vec_index, NULL);
    tac_join(expr, tac_ref);
    return tac_ref;
}

TAC* do_args(TAC* expr, TAC* args)
{
    TAC* tac_arg = tac_create(TAC_ARG, expr->res, NULL, NULL);
    tac_join(args, expr);
    tac_join(expr, tac_arg);

    return tac_arg;
}

HASH_NODE** params;
void set_declared_parameters(HASH_ENTRY* func_call)
{
    params = (HASH_NODE**)calloc(10, sizeof(HASH_NODE*));
    AST* param_list_node = func_call->ast_declaration_node->sons[1];

    int i = 0;
    while(param_list_node && i < 10) {
        // olhar o parser.y para entender melhor...
        params[i] = param_list_node->sons[0]->symbol[0];
        param_list_node = param_list_node->sons[1];
        i++;
    }
}

TAC* do_func_call(HASH_NODE* func_name, TAC* args_list)
{
    set_declared_parameters(func_name->data);
    TAC* tac_call = tac_create(TAC_CALL, func_name, NULL, NULL);
    tac_join(args_list, tac_call); // [CALL]::[ARG]::[ARG]::...

    int i = 0;
    while(args_list) {

        args_list->op1 = params[i];
        i++;
        args_list = args_list->prev;
        if (args_list) {
            args_list = args_list->prev;
        }
    }

    return tac_call;
}

void ast_init_tac_result_vec(AST* root, TAC* tac_children[])
{
    int i = 0;
    for (i = 0; i < AST_MAX_SONS; ++i) {
        if (root->sons[i]) {
            tac_children[i] = ast_generate_tac_code(root->sons[i]);

        } else {
            tac_children[i] = NULL;
        }
    }
}


TAC* ast_generate_tac_code(AST *root)
{
    
    TAC* tac_children[AST_MAX_SONS];
    ast_init_tac_result_vec(root, tac_children);
    
    switch(root->type) {
        case AST_IDENTIFIER:
        case AST_LIT_INT:
        case AST_LIT_CHAR: return tac_create(TAC_REF, root->symbol[0], 0, 0);
        case AST_VEC_REF: return do_vec_ref(root->symbol[0], tac_children[0]);

        case AST_ASS: return do_assign(root->symbol[0], tac_children[0]);
        case AST_ASS_VEC: return do_vec_assign(root->symbol[0], tac_children[0], tac_children[1]);
        case AST_RETURN: return do_return(tac_children[0]);

        // Precisa ter um do_print_number e um do_print_string,
        // pra poder colocar as formatações %d ou %s
        case AST_PRINT_STRING: return do_print_str(root->symbol[0]);
        case AST_PRINT_EXPR: return do_print_int(tac_children[0]);

        case AST_LCMD:
        case AST_DEFS:
        case AST_PARAM_LIST: return tac_join(tac_children[0], tac_children[1]);

        case AST_PARAM: return do_func_param(root->symbol[0], tac_children[0]);
        case AST_CMD_BLOCK: return tac_children[0];
        case AST_VAR_DEF: return do_var_def(root->symbol[0], tac_children[0]);
        case AST_VEC_DEF: return do_vec_def(root->symbol[0], root->symbol[1], tac_children[0]);
        case AST_INT_TYPE: return tac_create(TAC_INT_TYPE, NULL, NULL, NULL);
        case AST_BYTE_TYPE: return tac_create(TAC_BYTE_TYPE, NULL, NULL, NULL);
        case AST_FUNC_DEF: return do_func_dec(root->symbol[0], tac_children[0], tac_children[1], tac_children[2]);
        case AST_ADD: return do_bin_op(TAC_ADD, tac_children[0], tac_children[1]);
        case AST_SUB: return do_bin_op(TAC_SUB, tac_children[0], tac_children[1]);
        case AST_MUL: return do_bin_op(TAC_MUL, tac_children[0], tac_children[1]);
        case AST_DIV: return do_bin_op(TAC_DIV, tac_children[0], tac_children[1]);
        case AST_IF: return do_if(tac_children[0],tac_children[1]);
        case AST_IF_ELSE: return do_if_else(tac_children[0],tac_children[1],tac_children[2]);
        case AST_OPERATOR_G: return do_bin_op(TAC_GRT,tac_children[0],tac_children[1]);
        case AST_OPERATOR_L: return do_bin_op(TAC_LESS,tac_children[0],tac_children[1]);
        case AST_OPERATOR_GE: return do_bin_op(TAC_GE,tac_children[0],tac_children[1]);
        case AST_OPERATOR_LE: return do_bin_op(TAC_LE,tac_children[0],tac_children[1]);
        case AST_OPERATOR_EQ: return do_bin_op(TAC_EQ,tac_children[0],tac_children[1]);
        case AST_OPERATOR_NE: return do_bin_op(TAC_NE,tac_children[0],tac_children[1]);
        case AST_OPERATOR_AND: return do_bin_op(TAC_AND,tac_children[0],tac_children[1]);
        case AST_OPERATOR_OR: return do_bin_op(TAC_OR,tac_children[0],tac_children[1]);
        case AST_EXPR_PAR: return tac_children[0];
        case AST_OPERATOR_NOT: return do_bin_op(TAC_NOT,tac_children[0],0);
        case AST_WHILE: return do_while(tac_children[0],tac_children[1]);
        case AST_FUNC_CALL: return do_func_call(root->symbol[0], tac_children[0]);
        case AST_FUNC_PARAM_LIST: return do_args(tac_children[0], tac_children[1]);

        // Should not happen...
        default: printf("[ERROR] UNKNOW AST NODE, TAC WAS NOT CREATED!\n"); return NULL;
    }
}

/**
 * Creates a tmp var to hold a string value from hash table
 * @param entry
 * @return
 */
char* code_gen_get_str_var(HASH_ENTRY* entry)
{
    char* var = calloc(1, sizeof(char));
    sprintf(var, "__%d", hash_address(entry->lexeme));

    return var;
}

/**
 * Generates a assembly declaration code for an identifier/literal from hash table
 * 
 * @param entry
 */
void code_gen_generate_data(HASH_ENTRY* entry)
{
    switch(entry->type) {
        
        //case SYMBOL_LIT_CHAR:
        //    code_gen_def_lit_char_var(assembly_code, entry);
        //    break;
        //case SYMBOL_LIT_INTEGER:
        //    code_gen_def_lit_int_var(assembly_code, entry);
        //    break;

        case SYMBOL_LIT_STRING:
            fprintf(assembly_code, "%s: .string \"%s\"\n", code_gen_get_str_var(entry), entry->lexeme);
            break;

        case SYMBOL_FUNC_DEC_PARAM:
        case SYMBOL_VAR_DEC:
            if (entry->dataType == SYMBOL_INT_TYPE) {
                code_gen_def_int_var(assembly_code, entry);

            } else if (entry->dataType == SYMBOL_BYTE_TYPE) {
                code_gen_def_byte_var(assembly_code, entry);
            }
            break;

        case SYMBOL_VEC_DEC:
            if (entry->dataType == SYMBOL_INT_TYPE) {
                code_gen_def_int_vec(assembly_code, entry);
                
            } else if (entry->dataType == SYMBOL_BYTE_TYPE) {
                code_gen_def_byte_vec(assembly_code, entry);
            }
            break;

    }
}

void code_gen_def_lit_int_var(FILE* assembly_code, HASH_ENTRY* hash_entry)
{
    //fprintf(assembly_code, ".globl _%s\n", hash_entry->lexeme);
    //fprintf(assembly_code, "\t.bss\n");
    //fprintf(assembly_code, "\t.align 4\n");
    //fprintf(assembly_code, "\t.type _%s, @object\n", hash_entry->lexeme);
    //fprintf(assembly_code, "\t.size _%s, 4\n", hash_entry->lexeme);
    fprintf(assembly_code, "_%s: .long %s\n", hash_entry->lexeme, hash_entry->lexeme);
}

void code_gen_def_lit_char_var(FILE* assembly_code, HASH_ENTRY* hash_entry)
{
    //fprintf(assembly_code, ".globl _%s\n", hash_entry->lexeme);
    //fprintf(assembly_code, "\t.bss\n");
    //fprintf(assembly_code, "\t.align \n");
    //fprintf(assembly_code, "\t.type _%s, @object\n", hash_entry->lexeme);
    //fprintf(assembly_code, "\t.size _%s, 1\n", hash_entry->lexeme);
    fprintf(assembly_code, "_%s: .byte %s\n", hash_entry->lexeme, hash_entry->lexeme);
}

void code_gen_def_int_var(FILE* assembly_code, HASH_ENTRY* hash_entry)
{
    //fprintf(assembly_code, ".globl %s\n", hash_entry->lexeme);
    //fprintf(assembly_code, "\t.bss\n");
    //fprintf(assembly_code, "\t.align 4\n");
    //fprintf(assembly_code, "\t.type %s, @object\n", hash_entry->lexeme);
    //fprintf(assembly_code, "\t.size %s, 4\n", hash_entry->lexeme);
    fprintf(assembly_code, "%s: .long 0\n", hash_entry->lexeme);
}

void code_gen_def_byte_var(FILE* assembly_code, HASH_ENTRY* hash_entry)
{
    //fprintf(assembly_code, ".globl %s\n", hash_entry->lexeme);
    //fprintf(assembly_code, "\t.type %s, @object\n", hash_entry->lexeme);
    //fprintf(assembly_code, "\t.size %s, 1\n", hash_entry->lexeme);
    fprintf(assembly_code, "%s: .byte 0\n", hash_entry->lexeme);
}

/**
 * Defines a vector of int. Each position of the vector needs 4 bytes in memory,
 * so, for a vector of 3 positions, we need 3 * 4 bytes in memory.
 * 
 * @param assembly_code
 * @param hash_entry
 */
void code_gen_def_int_vec(FILE* assembly_code, HASH_ENTRY* hash_entry)
{
    int i = 0;
    int vec_size = atoi(hash_entry->ast_declaration_node->symbol[1]->data->lexeme);
    fprintf(assembly_code, "%s:\n", hash_entry->lexeme);

    for(i = 0; i < vec_size; i++) {
        fprintf(assembly_code, "\t.long 0\n");
    }
}

/**
 * Defines a vector of bytes
 *
 * @param assembly_code
 * @param hash_entry
 */
void code_gen_def_byte_vec(FILE* assembly_code, HASH_ENTRY* hash_entry)
{
    int i = 0;
    int vec_size = atoi(hash_entry->ast_declaration_node->symbol[1]->data->lexeme);
    fprintf(assembly_code, "%s:\n", hash_entry->lexeme);

    for(i = 0; i < vec_size; i++) {
        fprintf(assembly_code, "\t.byte 0\n");
    }
}

/**
 * Generates code for the data segment (vars and const declarations)
 * @param hash
 */
void code_gen_generate_data_segment(HASH_TABLE* hash)
{
    fprintf(assembly_code, "# Data segment (global vars declarations)\n");
    fprintf(assembly_code, "\n\t.data\n");

    // used to print numbers
    fprintf(assembly_code, "num_format: .string \"%%d\"\n");

    //used to store values returned by functions
    fprintf(assembly_code, "\n# used to store values returned by functions\n");
    fprintf(assembly_code, "func_ret: .long 0\n");
    
    int i;

    for (i = 0; i < TABLE_SIZE; i++) {
        HASH_NODE* current_node = hash->entries[i];
        while(current_node != NULL) {
            code_gen_generate_data(current_node->data);
            current_node = current_node->next;
        }
    }
}

/**
 * Generates code for the text segment (the program code itself)
 * @param tac_code
 */
void code_gen_text_segment(TAC* tac_code)
{
    if (tac_code) {
        switch(tac_code->type) {

            case TAC_ADD: code_gen_add(assembly_code, tac_code);
            break;
            case TAC_SUB: code_gen_sub(assembly_code, tac_code);
            break;
            case TAC_MUL: code_gen_mul(assembly_code, tac_code);
            break;
            case TAC_DIV: code_gen_div(assembly_code, tac_code);
            break;
            case TAC_AND: code_gen_and(assembly_code, tac_code);
            break;
            case TAC_OR: code_gen_or(assembly_code, tac_code);
            break;
            case TAC_EQ: code_gen_comp(assembly_code, tac_code, code_gen_eq);
            break;
            case TAC_GRT: code_gen_comp(assembly_code, tac_code, code_gen_g);
            break;
            case TAC_LESS: code_gen_comp(assembly_code, tac_code, code_gen_l);
            break;
            case TAC_GE: code_gen_comp(assembly_code, tac_code, code_gen_ge);
            break;
            case TAC_LE: code_gen_comp(assembly_code, tac_code, code_gen_le);
            break;
            case TAC_NE: code_gen_comp(assembly_code, tac_code, code_gen_ne);
            break;
            case TAC_NOT: code_gen_not(assembly_code, tac_code);
            break;
            case TAC_JZERO: code_gen_jzero(assembly_code, tac_code);
            break;
            case TAC_GOTO: code_gen_goto(assembly_code, tac_code);
            break;
            case TAC_LABEL: code_gen_label(assembly_code, tac_code);
            break;
            case TAC_COPY: code_gen_copy(assembly_code, tac_code);
            break;
            case TAC_VEC_COPY: code_gen_vec_copy(assembly_code, tac_code);
            break;
            case TAC_VEC_REF: code_gen_vec_ref(assembly_code, tac_code);
            break;
            case TAC_FUNC_BEGIN: code_gen_func_begin(assembly_code, tac_code);
            break;
            case TAC_FUNC_END: code_gen_func_end(assembly_code, tac_code);
            break;
            case TAC_CALL: code_gen_call(assembly_code, tac_code);
            break;
            case TAC_PRINT_INT: code_gen_print_num(assembly_code, tac_code);
            break;
            case TAC_PRINT_STR: code_gen_print_str(assembly_code, tac_code);
            break;
            case TAC_RETURN: code_gen_return(assembly_code, tac_code);
            break;
            case TAC_ARG: code_gen_tac_arg(assembly_code, tac_code);
            break;
        }

        code_gen_text_segment(tac_code->prev);
    }
}

/**
 * Get the position in bytes to access a position in a vector
 * @param var
 * @param index
 */
int code_gen_get_vec_position(HASH_ENTRY* var, HASH_ENTRY* index)
{

    if (var->dataType == SYMBOL_INT_TYPE) {
        return atoi(index->lexeme) * 4;

    } else {
        return atoi(index->lexeme);
    }
}

/**
 * Generates Assembly code for MIPS R2000
 * 
 * @param tac_code The complete TAC code
 * @param hash hash table usede to generate the declared variables ans constants
 * @param assembly_file_name the file name where the generated assemby code will be written
 */
void code_gen(TAC* tac_code, HASH_TABLE* hash, char* assembly_file_name)
{
    assembly_code = fopen(assembly_file_name, "wt");

    code_gen_generate_data_segment(hash);
    fprintf(assembly_code, "\n# Code segment\n");
    fprintf(assembly_code, "\n\t.text\n");
    fprintf(assembly_code, "\t.globl __start\n");

    // call by default the main function with no args
    fprintf(assembly_code, "__start: call main\n");

    fprintf(assembly_code, "# call exit()\n");
    fprintf(assembly_code, "movl $1, %%eax\n");
    fprintf(assembly_code, "movl $0, %%ebx\n");
    fprintf(assembly_code, "int $0x80\n");

    code_gen_text_segment(tac_code);

    fclose(assembly_code);
}

void code_gen_add(FILE* assembly_code, TAC* tac_code)
{
    // AT&T Syntax: op src dest
    fprintf(assembly_code, "\n# ----- TAC_ADD -----\n");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op1->data, "%eax");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op2->data, "%ebx");
    fprintf(assembly_code, "addl %%ebx, %%eax\n");
    fprintf(assembly_code, "movl %%eax, %s\n", tac_code->res->data->lexeme);
}

void code_gen_sub(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "\n# ----- TAC_SUB -----\n");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op1->data, "%eax");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op2->data, "%ebx");
    fprintf(assembly_code, "subl %%ebx, %%eax\n");
    fprintf(assembly_code, "movl %%eax, %s\n", tac_code->res->data->lexeme);
    // AT&T Syntax: op src dest    
}

void code_gen_mul(FILE* assembly_code, TAC* tac_code)
{
    // AT&T Syntax: op src dest
    fprintf(assembly_code, "\n# ----- TAC_MUL -----\n");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op1->data, "%eax");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op2->data, "%ebx");
    fprintf(assembly_code, "imul %%ebx, %%eax\n");
    fprintf(assembly_code, "movl %%eax, %s\n", tac_code->res->data->lexeme);
}

void code_gen_div(FILE* assembly_code, TAC* tac_code)
{
    /*
     * No caso de divisão, deve ser fornecido apenas um parâmetro para o comando (o divisor),
     * enquanto o dividendo deve estar previamente carregado em %eax. O quociente é guardado
     * em %eax, enquanto o resto vai para %edx.
     */
    // AT&T Syntax: op src dest
    fprintf(assembly_code, "\n# ----- TAC_DIV -----\n");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op1->data, "%eax");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op2->data, "%ebx");
    //Para não dar FloatingPoint Exception, precede-se o idiv por "cdq"
    //fonte: http://www.openrce.org/blog/browse/EliCZ
    fprintf(assembly_code, "cdq\n");
    fprintf(assembly_code, "idiv %%ebx\n");
    fprintf(assembly_code, "movl %%eax, %s\n", tac_code->res->data->lexeme);
}

void code_gen_and(FILE* assembly_code, TAC* tac_code)
{
    // AT&T Syntax: op src dest
    code_gen_mov_op_to_reg(assembly_code, tac_code->op1->data, "%edx");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op2->data, "%ecx");
    fprintf(assembly_code, "and %%edx, %%ecx\n");
    fprintf(assembly_code, "movl %%ecx, %s\n", tac_code->res->data->lexeme);
}

void code_gen_or(FILE* assembly_code, TAC* tac_code)
{
    // AT&T Syntax: op src dest
    code_gen_mov_op_to_reg(assembly_code, tac_code->op1->data, "%edx");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op2->data, "%ecx");
    fprintf(assembly_code, "or %%edx, %%ecx\n");
    fprintf(assembly_code, "movl %%ecx, %s\n", tac_code->res->data->lexeme);
}

void code_gen_comp(FILE* assembly_code, TAC* tac_code, void (* fun)(FILE* assembly_code))
{
    // AT&T Syntax: op src dest
    fprintf(assembly_code, "\n# ----- TAC_CMP ----- (mensagem padrão para tac_grt, tac_eq etc)\n");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op1->data, "%edx");
    code_gen_mov_op_to_reg(assembly_code, tac_code->op2->data, "%eax");

    // compare the value of the registers
    fprintf(assembly_code, "cmpl %%eax, %%edx\n");

    fun(assembly_code);

    fprintf(assembly_code, "movb $0, %%ah\n");
    fprintf(assembly_code, "cmp %%al, %%ah\n");
}

void code_gen_eq(FILE* assembly_code)
{
    // sets the comparisson result in %al register
    fprintf(assembly_code, "sete %%al\n");
}

void code_gen_ne(FILE* assembly_code)
{
    // sets the comparisson result in %al register
    fprintf(assembly_code, "setne %%al\n");
}

void code_gen_g(FILE* assembly_code)
{
    // sets the comparisson result in %al register
    fprintf(assembly_code, "setg %%al\n");
}

void code_gen_l(FILE* assembly_code)
{
    // sets the comparisson result in %al register
    fprintf(assembly_code, "setl %%al\n");
}

void code_gen_ge(FILE* assembly_code)
{
    // sets the comparisson result in %al register
    fprintf(assembly_code, "setge %%al\n");
}

void code_gen_le(FILE* assembly_code)
{
    // sets the comparisson result in %al register
    fprintf(assembly_code, "setle %%al\n");
}

void code_gen_not(FILE* assembly_code, TAC* tac_code)
{
    // AT&T Syntax: op src dest
    code_gen_mov_op_to_reg(assembly_code, tac_code->op1->data, "%edx");
    fprintf(assembly_code, "notl %%edx\n");
    fprintf(assembly_code, "movl %%edx, %s\n", tac_code->res->data->lexeme);
}

void code_gen_jzero(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "jz %s\n", tac_code->res->data->lexeme);
}

void code_gen_label(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "\n# ----- TAC_LABEL ---");
    fprintf(assembly_code, "\n%s:\n", tac_code->res->data->lexeme);
}

void code_gen_copy(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "\n# ----- TAC_COPY ----\n");
    //if we are copying a function return to a var
    if(tac_code->op1->data->type == 10) {
        //it's a integer function
        if(tac_code->op1->data->dataType == 16) {
            fprintf(assembly_code, "movl func_ret, %%eax\n");
            //if we are copying a integer to a byte
            if(tac_code->res->data->dataType == 17) {
                fprintf(assembly_code, "movb %%al, %s\n", tac_code->res->data->lexeme);
            }
            //if we are copying a integer to an integer
            else {
                fprintf(assembly_code, "movl %%eax, %s\n", tac_code->res->data->lexeme);
            }
            
        }
        //it's a byte function
        else {
            //if we are copying a byte to an integer
            if(tac_code->res->data->dataType == 16) {
                fprintf(assembly_code, "movl $0, %s\n", tac_code->res->data->lexeme);
            }
            fprintf(assembly_code, "movb func_ret, %%al\n");
            fprintf(assembly_code, "movb %%al, %s\n", tac_code->res->data->lexeme);
        }

    }
    //if we are copying something else to a var
    else {
        code_gen_mov_op_to_reg(assembly_code, tac_code->op1->data, "%eax");
        fprintf(assembly_code, "movl %%eax, %s\n", tac_code->res->data->lexeme);
    }


}

void code_gen_vec_copy(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "\n# ----- TAC_VEC_COPY ----\n");
    int vec_position = code_gen_get_vec_position(tac_code->res->data, tac_code->op1->data);
    code_gen_mov_op_to_reg(assembly_code, tac_code->op2->data, "%eax");
    fprintf(assembly_code, "movl %%eax, %s+%d(%%rip)\n", tac_code->res->data->lexeme, vec_position);
}

void code_gen_func_begin(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "\n# =====> TAC_FUNC_BEGIN <=====\n");
    fprintf(assembly_code, ".globl %s\n", tac_code->res->data->lexeme);
    fprintf(assembly_code, "\t.type %s, @function\n", tac_code->res->data->lexeme);
    fprintf(assembly_code, "%s:\n", tac_code->res->data->lexeme);
    fprintf(assembly_code, "pushq %%rbp\nmovq %%rsp, %%rbp\n");
    if(strcmp(tac_code->res->data->lexeme, "main")) {
        //cleans return var
        fprintf(assembly_code, "movl $0, func_ret\n");
    }

}

void code_gen_func_end(FILE* assembly_code, TAC* tac_code)
{
    
    fprintf(assembly_code, "\nleave\nret\n");
    fprintf(assembly_code, "# =====> TAC_FUNC_END <=====\n\n");
}

void code_gen_call(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "\n# ----- TAC_CALL ----");
    fprintf(assembly_code, "\ncall %s\n", tac_code->res->data->lexeme);
}

void code_gen_mov_op_to_reg(FILE* assembly_code, HASH_ENTRY* entry, char* reg)
{
    if (entry->type == SYMBOL_LIT_INTEGER) {
        fprintf(assembly_code, "movl $%s, %s\n", entry->lexeme, reg);
    } else if (entry->type == SYMBOL_LIT_CHAR) {
        fprintf(assembly_code, "movl $%d, %s\n", atoi(entry->lexeme), reg);
    } else {
        fprintf(assembly_code, "movl %s, %s\n", entry->lexeme, reg);
    }
}

void code_gen_print_num(FILE* assembly_code, TAC* tac_code)
{
   fprintf(assembly_code, "\n# ----- TAC_PRINT ---\n");
    fprintf(assembly_code, "movl $num_format, %%eax\n");
    code_gen_mov_op_to_reg(assembly_code, tac_code->res->data, "%esi");
    fprintf(assembly_code, "movq %%rax, %%rdi\n");
    fprintf(assembly_code, "movl $0, %%eax\n");
    fprintf(assembly_code, "call printf\n");
    fprintf(assembly_code, "movl $0, %%eax\n");
}

void code_gen_print_str(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "\n# ----- TAC_PRINT ---\n");
    fprintf(assembly_code, "movl $%s, %%eax\n", code_gen_get_str_var(tac_code->res->data));
    fprintf(assembly_code, "movq %%rax, %%rdi\n");
    fprintf(assembly_code, "movl $0, %%eax\n");
    fprintf(assembly_code, "call printf\n");
    fprintf(assembly_code, "movl $0, %%eax\n");
}

void code_gen_goto(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "\n# ----- TAC_GOTO ----\n");
    fprintf(assembly_code, "jmp %s\n", tac_code->res->data->lexeme);
}

void code_gen_vec_ref(FILE* assembly_code, TAC* tac_code)
{
    int vec_position = code_gen_get_vec_position(tac_code->op1->data, tac_code->op2->data);

    fprintf(assembly_code, "movl %s+%d(%%rip), %%eax\n", tac_code->op1->data->lexeme, vec_position);
    fprintf(assembly_code, "movl %%eax, %s\n", tac_code->res->data->lexeme);
}

void code_gen_return(FILE* assembly_code, TAC* tac_code)
{
    fprintf(assembly_code, "\n# ----- TAC_RETURN --\n");
    if (tac_code->res->data->type == SYMBOL_LIT_INTEGER) {
        fprintf(assembly_code, "movl $%s, func_ret\n", tac_code->res->data->lexeme);
    }
    else if (tac_code->res->data->type == SYMBOL_LIT_CHAR) {
        fprintf(assembly_code, "movl $%d, func_ret\n", atoi(tac_code->res->data->lexeme));
    }
    else {
        fprintf(assembly_code, "movl %s, %%eax\n", tac_code->res->data->lexeme);
        fprintf(assembly_code, "movl %%eax, func_ret\n");
    }
}

void code_gen_tac_arg(FILE* assembly_code, TAC* tac_code)
{
    code_gen_mov_op_to_reg(assembly_code, tac_code->res->data, "%eax");
    fprintf(assembly_code, "movl %%eax, %s\n", tac_code->op1->data->lexeme);
}